'''
Cast class. A converstion utility used to convert text strings into native
python types.
Copyright (C) 2006  Jonathan Claggett

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public License along
with this library; if not, write to the Free Software Foundation, Inc., 51
Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

You may contact the author at <jonathan@claggett.org>
'''

import re

class Cast(object):
    '''
    The Cast class is used to automatically convert a string values into into
    native data types. For example: x="123.3" => x=123.3.
    '''

    # Type flags
    INT = INTEGER = 1
    HEX = HEXADECIMAL = 2
    FLOAT = 4
    PERCENT = 8
    BOOL = BOOLEAN = 16
    STR = STRING = 32
    NUMBER = INTEGER | FLOAT | PERCENT | HEXADECIMAL
    ANY = NUMBER | BOOLEAN | STRING

    type_pattern = re.compile(r'''
        ^(?:
            (?P<int>      [-+]?\d+ ) |
            (?P<hex>      0x[\da-f]+ ) |
            (?P<float>    [-+]?(\d+(\.\d*)?|\d*\.\d+)(e[-+]?\d+)? ) |
            (?P<percent>  [-+]?(\d+(\.\d*)?|\d*\.\d+)(e[-+]?\d+)? ) % |
            (?P<bool>     (true|false) ) |
            '?
                (?P<str>  .*? ) 
            '?
        )$
        ''', re.DOTALL | re.IGNORECASE | re.UNICODE | re.VERBOSE )

    def __call__(self, value, new_type=ANY):
        assert type(value) in (str, unicode)
        types = self.type_pattern.search(value).groupdict()

        if new_type & self.INTEGER and types['int'] != None:
            return int(types['int'])
        elif new_type & self.HEXADECIMAL and types['hex'] != None:
            return int(types['hex'], 16)
        elif new_type & self.FLOAT and types['float'] != None:
            return float(types['float'])
        elif new_type & self.PERCENT and types['percent'] != None:
            return float(types['percent']) / 100.0
        elif new_type & self.BOOLEAN and types['bool'] != None:
            return types['bool'].lower() == 'true'
        elif new_type & self.STRING and types['str'] != None:
            return types['str']
        else:
            return None

# Unit Testing
if __debug__:
    from unittest import TestCase

    class TestCast(TestCase):
        cast = Cast()
        def test_casting_numbers(self):
            self.assertEqual(100, self.cast('100'))
            self.assertEqual(255, self.cast('0xFF'))
            self.assertEqual(1.5, self.cast('1.5'))
            self.assertEqual(0.5, self.cast('50%'))

        def test_casting_booleans(self):
            self.assertEqual(True, self.cast('true'))
            self.assertEqual(False, self.cast('false'))

        def test_casting_strings(self):
            self.assertEqual('hello', self.cast("hello"))
            self.assertEqual('12345', self.cast("'12345"))
            self.assertEqual('12345', self.cast("12345'"))
            self.assertEqual('12345', self.cast("'12345'"))

    if __name__ == '__main__':
        from test import test_support
        test_support.run_unittest( TestCast )

# vim: ai et sw=4 ts=4
